﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ETModel;

namespace ETHotfix
{
	[ObjectSystem]
	public class SessionAwakeSystem : AwakeSystem<Session, ETModel.Session>
	{
		public override void Awake(Session self, ETModel.Session session)
		{
			self.session = session;
			SessionCallbackComponent sessionComponent = self.session.AddComponent<SessionCallbackComponent>();
			sessionComponent.MessageCallback = (s, header, memoryStream) => { self.Run(s, header, memoryStream); };
			sessionComponent.DisposeCallback = s => { self.Dispose(); };
		}
	}

	/// <summary>
	/// 用来收发热更层的消息
	/// </summary>
	public class Session: Entity
	{
		public ETModel.Session session;

		private static byte RpcId { get; set; }
		private readonly Dictionary<byte, Action<IInMessage>> requestCallback = new Dictionary<byte, Action<IInMessage>>();

		public override void Dispose()
		{
			if (this.IsDisposed)
			{
				return;
			}
			
			base.Dispose();

			foreach (Action<IInMessage> action in this.requestCallback.Values.ToArray())
			{
				action.Invoke(null);
			}

			this.requestCallback.Clear();

			this.session.Dispose();
		}

		public void Run(ETModel.Session s, IMHeader header, MemoryStream memoryStream)
		{
			OpcodeTypeComponent opcodeTypeComponent = Game.Scene.GetComponent<OpcodeTypeComponent>();
            IInMessage instance = opcodeTypeComponent.GetInputInstance(header.ID);
            if (instance == null) return;
            IInMessage message;
            if (header.ErrorCode >= 100)
            {
                message = instance;
            }
            else
            {
                message = (IInMessage)this.session.Network.MessagePacker.DeserializeFrom(instance, memoryStream);
            }
            instance.ErrorCode = header.ErrorCode;

            if (OpcodeHelper.IsNeedDebugLogMessage(header.ID))
			{
				Log.Msg(string.Format("Recive Message:{0}", message));
			}
			
			Action<IInMessage> action;
			if (!this.requestCallback.TryGetValue(header.RpcId, out action))
			{
                Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(header.ID, message));
            }
            else
            {
                this.requestCallback.Remove(header.RpcId);
                action(message as IInMessage);
            }

            header.Clear();
        }

		public void Send(IOutMessage message)
		{
			int id = Game.Scene.GetComponent<OpcodeTypeComponent>().GetOutputID(message.GetType());
			this.Send(id, message);
		}

		public void Send(int id, IOutMessage message)
		{
			if (OpcodeHelper.IsNeedDebugLogMessage(id))
			{
				Log.Msg(message);
			}
			session.Send(id, message);
		}

		public ETTask<IInMessage> Call(IOutMessage request)
		{
            RpcId = RpcIdGenerater.GenerateId();
            var tcs = new ETTaskCompletionSource<IInMessage>();

			this.requestCallback[RpcId] = (response) =>
			{
				try
				{
					if (ErrorCode.IsRpcNeedThrowException(session.Network.MessageHeader.ErrorCode))
					{
						throw new RpcException(session.Network.MessageHeader.ErrorCode, $"内部错误{session.Network.MessageHeader.ErrorCode}！");
					}

					tcs.SetResult(response);
				}
				catch (Exception e)
				{
					tcs.SetException(new Exception($"Rpc Error: {request.GetType().FullName}", e));
				}
			};

            session.Network.MessageHeader.RpcId = RpcId;
			
			this.Send(request);
			return tcs.Task;
		}

		public ETTask<IInMessage> Call(IOutMessage request, CancellationToken cancellationToken)
		{
            RpcId = RpcIdGenerater.GenerateId();
            var tcs = new ETTaskCompletionSource<IInMessage>();

			this.requestCallback[RpcId] = (response) =>
			{
				try
				{
                    if (ErrorCode.IsRpcNeedThrowException(session.Network.MessageHeader.ErrorCode))
                    {
                        throw new RpcException(session.Network.MessageHeader.ErrorCode, $"内部错误{session.Network.MessageHeader.ErrorCode}！");
                    }

                    tcs.SetResult(response);
				}
				catch (Exception e)
				{
					tcs.SetException(new Exception($"Rpc Error: {request.GetType().FullName}", e));
				}
			};

			cancellationToken.Register(() => { this.requestCallback.Remove(RpcId); });

            session.Network.MessageHeader.RpcId = RpcId;

			this.Send(request);
			return tcs.Task;
		}
	}
}
